package com.xiyuan.smartutils.crypto;

import com.xiyuan.smartutils.reflect.Unsafe;

/**
 * Unicode 转换器
 *
 * @author xiyuan-lgz 2022-10-25 @version v1.0.0 新建与整理
 */
@SuppressWarnings({"AlibabaLowerCamelCaseVariableNaming", "SameParameterValue"})
public final class Unicode {
    static {
        Unsafe.init();
    }
    private static final char ZERO = '0';
    private static final int SHIFT = 4;
    private static final String UNI_PREFIX = "\\u";
    /**
     *
     */
    private static final char[] DIGITS_HEX_ = "0123456789abcdef".toCharArray();
    
    @SuppressWarnings("UnnecessaryUnicodeEscape")
    public enum Lang {
        /**
         * 汉语 常用字符范围    'u4e00' - 'u9fa5'， 但汉字最大字符范围为 '\\u9fd5'
         */
        zh_CN('\u4e00', '\u9fd5');
        
        private final int min;
        private final int max;
        
        Lang(int min, int max) {
            this.min = min;
            this.max = max;
        }
        
        public int getMin() {
            return min;
        }
        
        public int getMax() {
            return max;
        }
        
        public boolean is(int c) {
            return this.min <= c && c <= this.max;
        }
    }
    
    /**
     * 自定国家语言部分 转换成unicode
     *
     * @param src  原字符串
     * @param lang 需要转码的语言
     * @return 转码后的字符串
     */
    private static String encrypt(String src, Lang lang) {
        if (src == null) {return null;}
        int length = src.length();
        final int shift = SHIFT;
        StringBuilder strb = new StringBuilder(Math.max(length, 2048));
        for (int i = 0; i < length; i++) {
            char ch = src.charAt(i);
            if (lang != null && !lang.is(ch)) {
                // 不需要unicode 转换直接加入当前字符串
                strb.append(ch);
                continue;
            }
            
            
            // 先补0
            strb.append(UNI_PREFIX);
            for (int j = 0; j < shift; j++) strb.append(ZERO);
            
            int mag = Integer.SIZE - Integer.numberOfLeadingZeros(ch);
            int chs = Math.max(((mag + (shift - 1)) / shift), 1);
            int radix = 1 << shift;
            int mask = radix - 1;
            int offset = strb.length();
            do {
                strb.setCharAt(--offset, DIGITS_HEX_[ch & mask]);
                ch >>>= shift;
                chs--;
            }
            while (ch != 0 && chs > 0);
        }
        return strb.toString();
    }
    
    /**
     * 将字符串换成 unicode
     *
     * @param src 原字符串
     * @return 转换后字符串
     */
    public static String encode(String src) {
        return encrypt(src, null);
    }
    
    /**
     * unicode 解码，原字符串必须 为 \\uxxxx 格式
     *
     * @param src 原字符串
     * @return 解码后字符串
     */
    public static String decode(String src) {
        if (src == null) {
            return null;
        }
        
        StringBuilder strb = new StringBuilder(src);
        int fromIndex = 0, endIndex, shift = 4, px = 2, redix = 16, unii;
        while ((fromIndex = strb.indexOf(UNI_PREFIX, fromIndex)) != -1) {
            endIndex = fromIndex + px + shift;
            if (endIndex > strb.length()) {
                break;// 字符长度不足以构建成 unicode 字符
            }
            
            int digit, uni = 0;
            boolean uni_scope = true, ved = false;
            unii = fromIndex + px;
            for (int i = 0; i < shift; i++) {
                char ch = strb.charAt(unii++);
                int d = ch - 48;
                // 0123456789ABCDEFabcdef
                uni_scope = (0 <= d && d <= 9) || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F');
                if (!uni_scope) {
                    fromIndex = unii;
                    break;// 当前不是 unicode 字符范围
                }
                
                if (!ved && ch == '0') continue;
                
                ved = true;
                digit = Character.digit(ch, redix);
                uni *= redix;
                uni -= digit;
                
                if (i == 3) {
                    strb.setCharAt(fromIndex, (char) -uni);
                    strb.delete(++fromIndex, endIndex);
                    
                    break;
                }
            }
        }
        
        return strb.toString();
    }
}
